#include "ZGif.h"
#include "ZThread.h"

namespace ZuiLib
{


ZGif::ZGif()
	:m_pGIF(NULL),m_width(0),m_height(0),m_curIndex(0),m_durTime(9999),m_imageData(NULL)
{

}

ZGif::~ZGif()
{
	DGifCloseFile(m_pGIF);
	free(m_imageData);
}

RefCountedPtr<ZGif> ZGif::Create()
{
	return new RefCountedObject<ZGif>();
}

struct GifUserData 
{
	const unsigned char* pData;
	int  nDataMark;
	int  nDataLen;
};

static int DecodeCallBackProc(GifFileType* gif, GifByteType* bytes, int size)
{
	GifUserData* data=(GifUserData*)gif->UserData;
    for(int i=0; i<size && data->nDataMark<data->nDataLen ; i++,data->nDataMark++)
    {
        bytes[i] = data->pData[data->nDataMark];
    }
    return size;
}

bool ZGif::LoadFromMem(const uint8_t* data,int len)
{
	if (m_pGIF)
	{
		DGifCloseFile(m_pGIF);
		free(m_imageData);
	}

	GifUserData udata;
    udata.pData = data;
	udata.nDataLen=len;
    if(!udata.pData || 0 == len)
    {
        return false;
    }
    udata.nDataMark = 0;
    int error = 0;
    m_pGIF = DGifOpen(&udata,&DecodeCallBackProc,&error);
    if (NULL == m_pGIF || DGifSlurp(m_pGIF) != GIF_OK)
    {
        DGifCloseFile(m_pGIF);
        m_pGIF = NULL;
        return false;
    }
    m_width = m_pGIF->SWidth;
    m_height = m_pGIF->SHeight;
    m_imageData = (unsigned char*)malloc(sizeof(unsigned char) * m_width * m_height * 4);
	memset(m_imageData,0,sizeof(unsigned char) * m_width * m_height * 4);
    this->setGifImageWithIndex(0);
	return true;
}

bool ZGif::LoadFromFile(const char* file)
{
	if (m_pGIF)
	{
		DGifCloseFile(m_pGIF);
		free(m_imageData);
	}

    int error = 0;
    m_pGIF = DGifOpenFileName(file,&error);
    if (NULL == m_pGIF || DGifSlurp(m_pGIF) != GIF_OK)
    {
        DGifCloseFile(m_pGIF);
        m_pGIF = NULL;
        return false;
    }
    m_width = m_pGIF->SWidth;
    m_height = m_pGIF->SHeight;
    m_imageData = (unsigned char*)malloc(sizeof(unsigned char) * m_width * m_height * 4);
	memset(m_imageData,0,sizeof(unsigned char) * m_width * m_height * 4);
    this->setGifImageWithIndex(0);
	return true;
}


class ZLoadGifCallback:public ZThreadCallback
{
public:
	ZLoadGifCallback(const char* filePath,RefCountedPtr<ZSchedulerBase> target,SEL_SCHEDULE_GIF selector)
		:m_filePath(filePath),m_target(target),m_selector(selector)
	{

	}

	virtual int OnRun()
	{
		ZGifPtr gif=ZGif::Create();
		if(!gif->LoadFromFile(m_filePath.c_str()))
		{
			gif=NULL;
		}

		if (m_target && m_selector)
		{
			(m_target->*m_selector)(gif);
		}
		return 0;
	}

private:
	std::string m_filePath;
	RefCountedPtr<ZSchedulerBase> m_target;
	SEL_SCHEDULE_GIF m_selector;
};


void ZGif::LoadAsync(const char* filePath,ZSchedulerBase *pTarget, SEL_SCHEDULE_GIF pfnSelector)
{
	ZThreadPool* poll=ZThreadPool::Instance();
	if (poll)
	{
		poll->PostRunable(new RefCountedObject<ZLoadGifCallback>(filePath,pTarget,pfnSelector));
	}
}


ZSize ZGif::GetSize()
{
	ZSize sz(m_width,m_height);
	return sz;
}


int ZGif::GetFrameCount()
{
	if (m_pGIF)
		return m_pGIF->ImageCount;
	else
		return 0;
}


bool ZGif::ReadFrame(ZTexture* texture,int idx)
{
	if(m_pGIF && texture->GetSize()==GetSize())
	{
		if (m_curIndex!=idx)
		{
			setGifImageWithIndex(idx);
		}

		ZRect rc(0,0,m_width,m_height);
		texture->SetPixels(rc,m_imageData,m_width*4);
		return true;
	}
	else
		return false;
}



void ZGif::copyLine(unsigned char* dst, const unsigned char* src, const ColorMapObject* cmap, int transparent, int width)
{
    for (; width > 0; width--, src++, dst+=4) {
        if (*src != transparent) {
            const GifColorType& col = cmap->Colors[*src];
            *dst     = col.Red;
            *(dst+1) = col.Green;
            *(dst+2) = col.Blue;
            *(dst+3) = 0xFF;
        }
    }
}


void ZGif::setGifImageWithIndex(unsigned int index)
{
    index = ZMIN(index, m_pGIF->ImageCount-1);
    m_curIndex = index;
    
    const SavedImage* cur = &m_pGIF->SavedImages[index];
    
    m_durTime = getImageDuration(cur);

    if (index == 0)
    {
        bool trans;
        int disposal;
        this->getTransparencyAndDisposalMethod(cur, &trans, &disposal);
    }
    else
    {
        const SavedImage* prev = &m_pGIF->SavedImages[index-1];
        
        bool curTrans;
        int curDisposal;
        this->getTransparencyAndDisposalMethod(prev, &curTrans, &curDisposal);
        bool nextTrans;
        int nextDisposal;
        this->getTransparencyAndDisposalMethod(cur, &nextTrans, &nextDisposal);
        
        if (nextTrans || !checkIfCover(cur, prev))
        {
            if (curDisposal == 2)
            {
                unsigned char* dst = &m_imageData[(prev->ImageDesc.Top * m_width + prev->ImageDesc.Left) * 4];
                GifWord copyWidth = prev->ImageDesc.Width;
                
                if (prev->ImageDesc.Left + copyWidth > m_width)
                {
                    copyWidth = m_width - prev->ImageDesc.Left;
                }
                
                GifWord copyHeight = prev->ImageDesc.Height;
                if (prev->ImageDesc.Top + copyHeight > m_height)
                {
                    copyHeight = m_height - prev->ImageDesc.Top;
                }
                
				for (int i=0;i< copyHeight;++i)
				{
					for(int wIndex = 0; wIndex < copyWidth; wIndex++, dst+=4)
					{
						*dst     = 0;
						*(dst+1) = 0;
						*(dst+2) = 0;
						*(dst+3) = 0;
					}
				}
            }
        }
    }
    
    {
        int transparent = -1;
        for (int i = 0; i < cur->ExtensionBlockCount; ++i)
        {
            ExtensionBlock* eb = cur->ExtensionBlocks + i;
            if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
                eb->ByteCount == 4)
            {
                bool has_transparency = ((eb->Bytes[0] & 1) == 1);
                if (has_transparency)
                {
                    transparent = (unsigned char)eb->Bytes[3];
                }
            }
        }
        

		ColorMapObject* sColorMap=m_pGIF->SColorMap;
		if (cur->ImageDesc.ColorMap)
		{
			sColorMap=cur->ImageDesc.ColorMap;
		}

        if (sColorMap && sColorMap->ColorCount == (1 << sColorMap->BitsPerPixel))
        {
            unsigned char* src = (unsigned char*)cur->RasterBits;
            unsigned char* dst = &m_imageData[(cur->ImageDesc.Top * m_width + cur->ImageDesc.Left) * 4];
            
            GifWord copyWidth = cur->ImageDesc.Width;
            if (cur->ImageDesc.Left + copyWidth > m_width)
            {
                copyWidth = m_width - cur->ImageDesc.Left;
            }
            
            GifWord copyHeight = cur->ImageDesc.Height;
            if (cur->ImageDesc.Top + copyHeight > m_height)
            {
                copyHeight = m_height - cur->ImageDesc.Top;
            }
            
            for (; copyHeight > 0; copyHeight--)
            {
                copyLine(dst, src, sColorMap, transparent, copyWidth);
				src += cur->ImageDesc.Width;
				dst += m_width*4;
            }
        }
    }
}


void ZGif::getTransparencyAndDisposalMethod(const SavedImage* frame, bool* trans, int* disposal)
{
    *trans = false;
    *disposal = 0;
    for (int i = 0; i < frame->ExtensionBlockCount; ++i)
    {
        ExtensionBlock* eb = frame->ExtensionBlocks + i;
        if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
            eb->ByteCount == 4)
        {
            *trans = ((eb->Bytes[0] & 1) == 1);
            *disposal = ((eb->Bytes[0] >> 2) & 7);
        }
    }
}

bool ZGif::checkIfCover(const SavedImage* target, const SavedImage* covered)
{
    if (target->ImageDesc.Left <= covered->ImageDesc.Left
        && covered->ImageDesc.Left + covered->ImageDesc.Width <=
        target->ImageDesc.Left + target->ImageDesc.Width
        && target->ImageDesc.Top <= covered->ImageDesc.Top
        && covered->ImageDesc.Top + covered->ImageDesc.Height <=
        target->ImageDesc.Top + target->ImageDesc.Height) {
        return true;
    }
    return false;
}

bool ZGif::checkIfWillBeCleared(const SavedImage* frame)
{
    for (int i = 0; i < frame->ExtensionBlockCount; ++i)
    {
        ExtensionBlock* eb = frame->ExtensionBlocks + i;
        if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
            eb->ByteCount == 4)
        {
            // check disposal method
            int disposal = ((eb->Bytes[0] >> 2) & 7);
            if (disposal == 2 || disposal == 3)
            {
                return true;
            }
        }
    }
    return false;
}

int ZGif::getImageDuration(const SavedImage* image)
{
    int duration = 0;
    for (int j = 0; j < image->ExtensionBlockCount; j++)
    {
        if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE)
        {
            int size = image->ExtensionBlocks[j].ByteCount;
            //assert(size >= 4);
            if(size < 4) break;
            const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes;
            duration =  ((b[2] << 8) | b[1]) * 10;
            break;
        }
    }
    duration = duration <= 50 ? 50 : duration;
    return duration;
}






bool ZGif::IsGif(const char* fileName)
{
	FILE* file = fopen(fileName,"rb");
	bool res = IsGifFile(file);
	fclose(file);
	return res;
}


bool ZGif::IsGifFile(FILE* file)
{
	if(file == NULL)
	{
		return false;
	}
	long index = ftell(file);
	bool res = false;
	char buf[GIF_STAMP_LEN];
	int read = fread(buf,1,GIF_STAMP_LEN,file);
	if (read == GIF_STAMP_LEN) {
		if (memcmp(GIF_STAMP,   buf, GIF_STAMP_LEN) == 0 ||
			memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
			memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
				res =  true;
		}
	}
	fseek(file,index,SEEK_SET);
	return res;
}






























}